package ElectricCraft.frames;

import ElectricCraft.drawer.CustomDrawer;
import ElectricCraft.misc.Point;
import ElectricCraft.misc.Rectangle;
import ElectricCraft.model.ChipModel;
import ElectricCraft.viewers.ChipViewer;
import ElectricCraft.viewers.NotificationListener;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;

/**
 * ChipViewerPane class.
 */
public class ChipViewerPane extends JPanel {
    protected final MainWindow parent;
    protected ChipModel model;

    protected final PathPane pathPane;
    protected final ModelPath modelPath;

	public ChipViewerPane(MainWindow parent, ChipModel model) {
        super(new BorderLayout());

        this.parent = parent;
        this.model = model;
        this.modelPath = new ModelPath(model);

        pathPane = new PathPane();
        pathPane.setBackground(Color.WHITE);
        add(pathPane, BorderLayout.NORTH);

        initMouseListeners();

		initModel();
	}

    public static final class ModelPath implements PathPane.Path {
        private final List<ChipModel> models = new ArrayList<ChipModel>();
        public ModelPath(ChipModel model) {
            models.add(model);
        }

        public String getName(int index) {
            return models.get(index).name;
        }

        public ChipModel get(int index) {
            return models.get(index);
        }

        public int size() {
            return models.size();
        }

        public void append(ChipModel model) {
            models.add(model);
        }

        public void strip(int index) {
            while (models.size() > index + 1) {
                models.remove(models.size() - 1);
            }
        }

        public ChipModel getRoot() { return get(0); }
        public ChipModel getLast() { return get(size() - 1); }
    }

    public final ModelPath getModelPath() {
        return modelPath;
    }

    public final ChipModel getModel() {
        return model;
    }

    protected ChipViewer createViewer() {
        return new ChipViewer();
    }

    protected boolean allowDragBy(MouseEvent event) {
        switch (event.getButton()) {
            case MouseEvent.BUTTON1:
            case MouseEvent.BUTTON3:
                return true;
        }
        return false;
    }

    protected ChipViewer viewer;
	protected CustomDrawer drawer;
    private final java.awt.Point viewportCenterItem = new java.awt.Point();
	private void initModel() {
        viewer = createViewer();
        viewer.setModel(model);

		drawer = new CustomDrawer();
		viewer.setDrawer(drawer);

        viewer.addViewUpdatedListener(
                new NotificationListener() {
                    public void notified() {
                        repaint();
                    }
                });

        updateViewportCenter();
	}

    protected void updateViewportCenter() {
        Rectangle range = model.calcElementsRange();
        viewportCenterItem.setLocation(
                (range.x + range.width / 2.f) * drawer.getItemSize(),
                (range.y + range.height / 2.f) * drawer.getItemSize());

        if (range.width ==0 || range.height == 0) {
            viewportCenterItem.setLocation(
                    model.elements.length * drawer.getItemSize() / 2.f,
                    model.elements.length * drawer.getItemSize() / 2.f);
        }
    }

    protected void setModel(ChipModel model) {
        this.model = model;
        viewer.setModel(model);
        updateViewportCenter();

        repaint();
    }

	protected final java.awt.Rectangle paneBounds = new java.awt.Rectangle();

	private void initMouseListeners() {
		MouseAdapter adapter = new MouseAdapter() {
            private boolean isMoveMode   = false;
            private final Point prevMovePos = new Point();

			@Override
			public void mousePressed(MouseEvent mouseEvent) {
                getRootPane().getBounds(paneBounds);

                int x = mouseEvent.getX();
                int y = mouseEvent.getY();

                if (!paneBounds.contains(x, y)) {
                    return ;
                }

                if (mouseEvent.getButton() == MouseEvent.BUTTON1 && mouseEvent.getClickCount() == 2) {
                    int index = viewer.getChipUnderCursor(x, y);
                    if (index == -1) {
                        return ;
                    }

                    modelPath.append(model.models.get(index));
                    processOpenedSubModel();

                    mouseEvent.consume();
                    return ;
                }

                if (mouseEvent.isConsumed()) {
                    return ;
                }

                if (allowDragBy(mouseEvent)) {
                    isMoveMode = true;
                    prevMovePos.setLocation(mouseEvent.getX(), mouseEvent.getY());

                    mouseEvent.consume();
                }
			}

			@Override
			public void mouseReleased(MouseEvent mouseEvent) {
                isMoveMode   = false;

				getRootPane().getBounds(paneBounds);
			}

            private void processMove(MouseEvent mouseEvent) {
                getRootPane().getBounds(paneBounds);
                int x = mouseEvent.getX();
                int y = mouseEvent.getY();

                if (isMoveMode) {
                    viewportCenterItem.x -= x - prevMovePos.x;
                    viewportCenterItem.y -= y - prevMovePos.y;
                    prevMovePos.setLocation(x, y);

                    repaint();
                }
            }

			@Override
			public void mouseMoved(MouseEvent mouseEvent) {
                processMove(mouseEvent);
			}

			@Override
			public void mouseDragged(MouseEvent mouseEvent) {
                processMove(mouseEvent);
			}
		};

		addMouseListener(adapter);
		addMouseMotionListener(adapter);

        pathPane.addPathChangedListener(new PathPane.PathChangedListener() {
            public void pathChanged(PathPane.Path path, int index) {
                assert path == modelPath;
                modelPath.strip(index);
                processOpenedSubModel();
            }
        });
	}

    protected void processOpenedSubModel() {
        ChipModel model = modelPath.getLast();
        setModel(model);

        pathPane.setPath(modelPath.size() > 1 ? modelPath : null);

        parent.updateModelNames();
    }

	@Override
	public void paint(Graphics gr) {
        super.paint(gr);

		if (drawer == null || viewer == null) {
			return ;
		}

		Graphics2D gr2 = (Graphics2D)gr;

		drawer.setGraphics(gr2);

		getBounds(paneBounds);

		viewer.setViewport(
			viewportCenterItem.x - paneBounds.width  / 2,
			viewportCenterItem.y - paneBounds.height / 2,
			paneBounds.width,
			paneBounds.height);

		viewer.draw();

        pathPane.draw(drawer, gr);
	}
}
